/*
 * $Id: scm.c,v 1.5 2006-02-21 09:03:59 bacon Exp $
 */

#include <xp/msw/scm.h>
#include <xp/bas/memory.h>
#include <windows.h>


static int __control_service (
	xp_scm_t* scm, const xp_char_t* name, int code, xp_svcstatus_t* svcstatus);

static int __trans_error_code (int err);
static void __trans_service_status (
	SERVICE_STATUS* status, xp_svcstatus_t* svcstatus);

#define SCM_HANDLE(scm) ((SC_HANDLE)((scm)->handle))

xp_scm_t* xp_scm_open (xp_scm_t* scm, const xp_char_t* host)
{
	SC_HANDLE handle;

	if (scm == XP_NULL) {
		scm =  (xp_scm_t*) xp_malloc (xp_sizeof(xp_scm_t));
		if (scm == XP_NULL) return XP_NULL;
		scm->__dynamic = xp_true;
	}
	else scm->__dynamic = xp_false;

	handle = OpenSCManager (host, XP_NULL, SC_MANAGER_ALL_ACCESS);
	if (handle == XP_NULL)  {
		if (scm->__dynamic) xp_free (scm);
		else scm->errnum = __trans_error_code (GetLastError());
		return XP_NULL;
	}

	scm->handle = (void*)handle;
	return scm;
}

int xp_scm_close (xp_scm_t* scm)
{
	if (CloseServiceHandle((SC_HANDLE)scm->handle) == FALSE) {
		scm->errnum = __trans_error_code (GetLastError());
		return -1;
	}

	if (scm->__dynamic) xp_free (scm);
	return 0;
}

int xp_scm_geterrnum (xp_scm_t* scm)
{
	return scm->errnum;	
}

const xp_char_t* xp_scm_geterrstr (xp_scm_t* scm)
{
	static const xp_char_t* __errstr[] = 
	{
		XP_TEXT("no error"),
		XP_TEXT("access denied"),
		XP_TEXT("dependent service running"),
		XP_TEXT("invalid handle"),
		XP_TEXT("invalid parameter"),
		XP_TEXT("invalid service control"),
		XP_TEXT("invalid service name"),
		XP_TEXT("service cannot accept control code"),
		XP_TEXT("service not active"),
		XP_TEXT("request timed out"),
		XP_TEXT("system is shutting down"),
		XP_TEXT("non-existent service"),
		XP_TEXT("service that has been marked for deletion"),
		XP_TEXT("no such database found"),
		XP_TEXT("path not found"),
		XP_TEXT("service already running"),

		XP_TEXT("database locked"),
		XP_TEXT("depending service missing or deleted"),
		XP_TEXT("depending service failed to start"),
		XP_TEXT("service disabled"),
		XP_TEXT("serviced failed to start due to logon failure"),
		XP_TEXT("thread could not be created")
	};

	if (scm->errnum >= 0 && scm->errnum < xp_countof(__errstr)) 
		return __errstr[scm->errnum];

	return XP_TEXT("unknown error");
}

int xp_scm_createsvc (xp_scm_t* scm, const xp_char_t* name, const xp_char_t* path)
{
// TODO:....
/*
	SC_HANDLE sh;
	char* disp_name;

	disp_name = property.dispName();
	if (!dispName) dispName = (char*)svcName;

	sh = CreateService (SCM_HANDLE(scm), name, disp_name, 
		SERVICE_ALL_ACCESS, property.svcType(), 
		property.startType(), property.errCtrl(), 
		path, 0, 0, 0, 0, 0);
	if (sh == XP_NULL) return -1;

	CloseServiceHandle (sh);
	return 0;
*/
	return -1;
}

int xp_scm_deletesvc (xp_scm_t* scm, const xp_char_t* name)
{
	SC_HANDLE sh;
	int ret;
	
	sh = OpenService (SCM_HANDLE(scm), name, SERVICE_ALL_ACCESS);
	if (sh == XP_NULL) {
		scm->errnum = __trans_error_code (GetLastError());
		return -1;
	}

	ret = (DeleteService(sh) == TRUE)? 0: -1;
	if (ret == -1) {
		scm->errnum = __trans_error_code (GetLastError());
	}

	CloseServiceHandle (sh);
	return ret;
}

int xp_scm_startsvc (xp_scm_t* scm, const xp_char_t* name)
{
	SC_HANDLE sh;
	int ret;

	sh = OpenService (SCM_HANDLE(scm), name, SERVICE_ALL_ACCESS);
	if (sh == XP_NULL) {
		scm->errnum = __trans_error_code (GetLastError());
		return -1;
	}

	ret = (StartService(sh, 0, 0) == TRUE)? 0: -1;
	if (ret == -1) {
		scm->errnum = __trans_error_code (GetLastError());
	}
	
	CloseServiceHandle (sh);	

	return ret;
}

int xp_scm_stopsvc (xp_scm_t* scm, const xp_char_t* name)
{
	return __control_service (scm, name, SERVICE_CONTROL_STOP, XP_NULL);
}

int xp_scm_pausesvc (xp_scm_t* scm, const xp_char_t* name)
{
	return __control_service (scm, name, SERVICE_CONTROL_PAUSE, XP_NULL);
}

int xp_scm_resumesvc (xp_scm_t* scm, const xp_char_t* name)
{
	return __control_service (scm, name, SERVICE_CONTROL_CONTINUE, XP_NULL);
}

int xp_scm_getsvcstatus (xp_scm_t* scm, const xp_char_t* name, xp_svcstatus_t* svcstatus)
{
	return __control_service (scm, name, SERVICE_INTERROGATE, svcstatus);
}

static int __control_service (
	xp_scm_t* scm, const xp_char_t* name, int code, xp_svcstatus_t* svcstatus)
{
	SC_HANDLE sh;
	SERVICE_STATUS status;
	DWORD err;
	int ret;

	sh = OpenService(SCM_HANDLE(scm), name, SERVICE_ALL_ACCESS);
	if (sh == XP_NULL) {
		scm->errnum = __trans_error_code (GetLastError());
		return -1;
	}

	ret = (ControlService(sh, code, &status) == TRUE)? 0: -1;

	if (ret == 0) {
		if (svcstatus != XP_NULL) {
			__trans_service_status (&status, svcstatus);
		}
	}
	else {
		err = GetLastError ();
		if (ret == -1) scm->errnum = __trans_error_code (err);
		if (svcstatus != XP_NULL) {
			if (err == NO_ERROR || 
			    err == ERROR_INVALID_SERVICE_CONTROL ||
			    err == ERROR_SERVICE_CANNOT_ACCEPT_CTRL || 
			    err == ERROR_SERVICE_NOT_ACTIVE) {
				__trans_service_status (&status, svcstatus);
			}
		}
	}

	CloseServiceHandle (sh);
	return ret;
}

static int __trans_error_code (int err)
{
	if (err == ERROR_ACCESS_DENIED) 
		return XP_SCM_EACCDN;
	else if (err == ERROR_DEPENDENT_SERVICES_RUNNING) 
		return XP_SCM_EDEPRN;
	else if (err == ERROR_INVALID_HANDLE) 
		return XP_SCM_EINHND;
	else if (err == ERROR_INVALID_NAME) 
		return XP_SCM_EINNAM;
	else if (err == ERROR_INVALID_PARAMETER) 
		return XP_SCM_EINPAR;
	else if (err == ERROR_INVALID_SERVICE_CONTROL) 
		return XP_SCM_EINSVC;
	else if (err == ERROR_SERVICE_CANNOT_ACCEPT_CTRL) 
		return XP_SCM_ENOACC;
	else if (err == ERROR_SERVICE_NOT_ACTIVE) 
		return XP_SCM_ENOACT;
	else if (err == ERROR_SERVICE_REQUEST_TIMEOUT) 
		return XP_SCM_ETMOUT;
	else if (err == ERROR_SHUTDOWN_IN_PROGRESS) 
		return XP_SCM_ESHTDN;
	else if (err == ERROR_SERVICE_DOES_NOT_EXIST)
		return XP_SCM_ENOSVC;
	else if (err == ERROR_SERVICE_MARKED_FOR_DELETE)
		return XP_SCM_ESVCMD;
	else if (err == ERROR_DATABASE_DOES_NOT_EXIST)
		return XP_SCM_ENODAB;
	else if (err == ERROR_PATH_NOT_FOUND)
		return XP_SCM_ENOPTH;
	else if (err == ERROR_SERVICE_ALREADY_RUNNING)
		return XP_SCM_ESVCRN;

	else if (err == ERROR_SERVICE_DATABASE_LOCKED)
		return XP_SCM_EDBLCK;
	else if (err == ERROR_SERVICE_DEPENDENCY_DELETED)
		return XP_SCM_EDEPDL;
	else if (err == ERROR_SERVICE_DEPENDENCY_FAIL)
		return XP_SCM_EDEPFL;
	else if (err == ERROR_SERVICE_DISABLED)
		return XP_SCM_EDSABL;
	else if (err == ERROR_SERVICE_LOGON_FAILED)
		return XP_SCM_ELOGON;
	else if (err == ERROR_SERVICE_NO_THREAD)
		return XP_SCM_ENOTHR;

	return XP_SCM_ENOERR;
}

static void __trans_service_status (SERVICE_STATUS* status, xp_svcstatus_t* svcstatus)
{
	svcstatus->type = 0;
	svcstatus->state = 0;

	if (status->dwServiceType & SERVICE_FILE_SYSTEM_DRIVER) 
		svcstatus->type |= XP_SVC_FILE_SYSTEM_DRIVER;
	if (status->dwServiceType & SERVICE_KERNEL_DRIVER) 
		svcstatus->type |= XP_SVC_KERNEL_DRIVER;
	if (status->dwServiceType & SERVICE_WIN32_OWN_PROCESS) 
		svcstatus->type |= XP_SVC_WIN32_OWN_PROCESS;
	if (status->dwServiceType & SERVICE_WIN32_SHARE_PROCESS) 
		svcstatus->type |= XP_SVC_WIN32_SHARE_PROCESS;
	if (status->dwServiceType & SERVICE_INTERACTIVE_PROCESS) 
		svcstatus->type |= XP_SVC_INTERACTIVE_PROCESS;

	if (status->dwCurrentState == SERVICE_STOPPED) 
		svcstatus->state = XP_SVC_STOPPED;
	else if (status->dwCurrentState == SERVICE_START_PENDING) 
		svcstatus->state = XP_SVC_START_PENDING;
	else if (status->dwCurrentState == SERVICE_STOP_PENDING) 
		svcstatus->state = XP_SVC_STOP_PENDING;
	else if (status->dwCurrentState == SERVICE_RUNNING) 
		svcstatus->state = XP_SVC_RUNNING;
	else if (status->dwCurrentState == SERVICE_CONTINUE_PENDING) 
		svcstatus->state = XP_SVC_RESUME_PENDING;
	else if (status->dwCurrentState == SERVICE_PAUSE_PENDING) 
		svcstatus->state = XP_SVC_PAUSE_PENDING;
	else if (status->dwCurrentState == SERVICE_PAUSED) 
		svcstatus->state = XP_SVC_PAUSED;
}
